#include<stdio.h>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<string>
#include <fstream>
#include<vector>
#include<map>
#include<string.h>
using namespace std;

/* 
	CQU-compiler-lab1 
	zhouyiqun  2022.4
	本词法分析代码基于课程教材的状态机实现 
*/

//!--- 输入输出文件 ---
//FILE *fpin=fopen(".\\testfiles\\6\\testfile1.txt", "r");
//ofstream fout(".\\testfiles\\6\\1_output.txt");
FILE *fpin;
ofstream fout;

//!--- 全局变量 ---
map<string, string> m;//用于检索保留字。只支持小写检索，检索函数进行了大小写转换
map<char, string> cm;//用于检索简单符号。简单符号：只需一个字符即可判断
int filesize;//源代码文件的大小
int state=0; //状态机
char c;//当前读入的字符
string token="";//正在处理的字符串
int mode;

//!--- 功能函数 ---

bool isDigit(char c){//判断数字
	return c>='0'&& c<='9';
}
bool isLetter(char c){//判断英文字母
	return c>='a'&& c<='z'||c>='A'&& c<='Z'||c=='_';//TODO ERROR line2 字符串无法识别
}

bool checkchar(char c){//检查字符常量的合法性
	bool isOp= c=='+' || c=='-' || c=='*' || c=='/'|| c=='=';
	bool isNum= isDigit(c);
	bool isLt= isLetter(c);
	return isOp|| isNum||isLt; //是运算符或数字或字母即合法
}
bool checkstr(char c){//检查字符串的合法性
	return c==32 || c==33 ||(c>=35 && c<=126);//参照文法定义的要求
}

void init(){//初始化检索保留字、简单符号的map
		m["const"]="CONSTTK";
		m["int"]="INTTK";
		m["char"]="CHARTK";
		m["void"]="VOIDTK";
		m["main"]="MAINTK";
		m["if"]="IFTK";
		m["else"]="ELSETK";
		m["switch"]="SWITCHTK";
		m["case"]="CASETK";
		m["default"]="DEFAULTTK";
		m["while"]="WHILETK";
		m["for"]="FORTK";
		m["scanf"]="SCANFTK";
		m["printf"]="PRINTFTK";
		m["return"]="RETURNTK";
		cm['+']="PLUS";
		cm['-']="MINU";
		cm['*']="MULT";
		cm['/']="DIV";
		cm['(']="LPARENT";
		cm[')']="RPARENT";
		cm[';']="SEMICN";
		cm[':']="COLON";
		cm['[']="LBRACK";
		cm[']']="RBRACK";
		cm['{']="LBRACE";
		cm['}']="RBRACE";
		cm[',']="COMMA";
}
string keylookup(string s){//将字符串转为小写，并检索是否为保留字
	for(int i=0; i<s.size(); i++){
		s[i]= tolower(s[i]);
	}
	return m[s];
}
void get_nbc(){//跳过源文件中的空格 //TODO 用-1判断是否合理
	while(int(c)==-1){ 
		fpin+=1;
		c= fgetc(fpin);	
	}
}

void normal_output(string cls, string word){ //正常情况下的输出，输出词+ 类别
	if(mode==3 || mode==1){//mode=3: 全部输出， mode=1 关闭错误输出	
		fout << cls << " "<< word<< endl;
	}
}


void normal_output(string cls, char word){ //正常情况, 函数重载
	if(mode==3 || mode==1){//mode=3: 全部输出， mode=1 关闭错误输出	
		fout << cls << " "<< word<< endl;
	}
}


void raise_error(int line_n, char e_code){//错误输出，输出行数+ 错误代码
	cout << "*_An Error Occurs at line "<< line_n<< " _ "<< e_code<<"_*" << endl;
	if(mode==2 || mode==3){//mode=3: 全部输出， mode=1 关闭错误输出	
		fout <<line_n+1<<"  "<<e_code<< endl;//计数的行数比实际少1
	}
}

//!--- 词法分析 ---
void lex_analyse(){
	int linenum= 0;
	do{//循环执行，直到文件位置标识符抵达文件尾
		switch(state){
			case 0://初始状态，根据下一个字符，决定跳转的状态（每个状态都是如此）
				token="";//将字串清空 
				c=fgetc(fpin); //输入下一个字符，并将文件位置符后移
				if(c=='\n'){//计算行号 
					linenum+=1;
				}
				if(int(c)==-1){//跳过空格
					get_nbc();
				}
				
				if(isLetter(c)||c=='_'){//判断标识符/关键字开头
					state= 1;
					break;
				}	
				else if(isDigit(c)) { //判断常数开头
					state= 2;
					break;
				}
				else if(cm[c]!=""){//判断简单符号
					normal_output(cm[c], c);//简单符号可直接判断，将判断结果写入输出。
					state= 0;//返回初始状态
					break;
				}
				else{//判断复合符号
					switch(c){
						case '<': 
							state=3;
							break;
						case '>': 
							state=4;
							break;
						case '=':  
							state=5;
							break;
						case '!': 
							state=6;
							break;
						case '\'': //字符串常量
							state=7;
							break;
						case '"': //字符串
							state=8;
							break;
						
						default: 
						
							break;					
					}
				}
				break;
			case 1: //识别标识符和关键字
				token+=c;//将当前字符加入正在识别的字符串
				c=fgetc(fpin);//读入下一个字符，并移动文件位置指示符
				if(isDigit(c)||isLetter(c)||c=='_'){//除开头外，标识符可以由字母、数字、下划线组成。输入其他符号说明标识符已结束
					state= 1;
					break;
				}
				else{//下一个字符与标识符无关，标识符到此为止。进一步排除区别关键字/标识符
					fseek(fpin, -1, SEEK_CUR);// 文件位置符后退一位，消除fgetc的影响，使得新的字符能够在初始状态被重新处理。需用fseek操作FILE对象的文件位置符
					state=0;
					if(keylookup(token)!=""){ //写入关键字
						normal_output(keylookup(token), token);

					}else{//写入标识符
						normal_output("IDENFR ", token);
					}
				}
				break;
			case 2://识别整数常数
				token+=c;
				c=fgetc(fpin);
				if(isDigit(c)){//输入非数字说明数字已结束
					state= 2;
					break;
				}
				else{ //下一个字符与整数常量无关，整数常量到此为止。
					fseek(fpin, -1, SEEK_CUR);
					state= 0;
					normal_output( "INTCON ",token);//写入整数常量
				}
				break;
			case 3://小于号 
				token+=c;
				c=fgetc(fpin);
				if(c=='='){//下一个字符为等号，识别为LEQ
					token+=c;//将等号插入当前识别的字符串，以便输出
					normal_output("LEQ " ,token);
					state= 0;//当前字符串识别完毕，接下来回到初始状态
					break;
				}
				else{//下一个不为等号，识别为LSS
					fseek(fpin, -1, SEEK_CUR);//位置符回退
					state= 0;//接下来返回初始状态
					normal_output("LSS ",token);
					break;
				}
				break;
			case 4://大于号
				token+=c;
				c=fgetc(fpin);
				if(c=='='){//下一个字符为等号，识别为GEQ
					token+=c;
					normal_output("GEQ ",token);
					state= 0;
					break;
				}
				else{//下一个不为等号，识别为GRE
					fseek(fpin, -1, SEEK_CUR);
					state= 0;
					normal_output("GRE ",token);
					break;
				}
				break;
			case 5://等号
				token+=c;
				c=fgetc(fpin);
				if(c=='='){//下一个字符为等号，识别为EQL
					token+=c;
					normal_output("EQL ",token);
					state= 0;
					break;
				}
				else{//下一个字符不为等号，识别为ASSIGN
					fseek(fpin, -1, SEEK_CUR);
					state= 0;
					normal_output("ASSIGN ",token);
					break;
				}
				break;
			case 6://感叹号!
				token+=c;
				c=fgetc(fpin);
				if(c=='='){//下一个字符为=，识别为NEQ
					token+=c;
					normal_output("NEQ ",token);
					break;
				}
				else{//下一个字符不为=，出错
					fseek(fpin, -1, SEEK_CUR);
					state= 0;
					raise_error(linenum,'a');//$ 感叹后号后不为等号，a类错误
					break;
				}
				break;
			case 7://单引号'(字符常量的左引号)
				c=fgetc(fpin);
				if(checkchar(c)){//输入合法字符
					token+=c;
					state=9;
					break;
				}
				else{//输入非法字符，包括单引号为空的情况
					//cout << "wrong char for const"<< endl;
					raise_error(linenum, 'a');//$ 字符常量中字符为空或非法，a类错误
					c=fgetc(fpin);
					if(c=='\''){//若后面还有一个单引号
						state=0;
					}else{
						fseek(fpin, -1, SEEK_CUR);
						state=0;
					}
				}
				break;
			case 8://双引号" (字符串的左引号)
				c=fgetc(fpin);
				if(checkstr(c)){//合法的字符串字符
					token+= c;
					state=10;
					break;
				}
				else{//非法的字符串字符，包括了双引号（因为不能为空）
					// cout <<"illegal char for str"<<endl;
					raise_error(linenum, 'a');//$ 字符串常量中字符为空或非法，a类错误
					c=fgetc(fpin);
					if(c=='\"'){//若后面还有一个双引号
						state=0;
					}else{//回退
						fseek(fpin, -1, SEEK_CUR);
						state=0;
					}
				}
				break;
			case 9://字符常量，读入了一个字符
				c=fgetc(fpin);
				if(c=='\''){//下一个字符为'，即字符常量的右引号，字符常量结束  、
					normal_output("CHARCON ",token);
					state=0;
					break;
				}
				else{//输入的不是右单引号，超过一个字符被赋给字符常量
					// cout <<"more than one char"<<endl;
					state= 0;
					break;
				}
			case 10://字符串常量，读入了一个字符
				c=fgetc(fpin);
				if(c=='"'){//下一个字符是右边的双引号，终止
					normal_output("STRCON ",token);
					state= 0;
					break;
				}
				else{
					if(checkstr(c)){//下一个字符仍然为合法字符串字符，继续回到state 10
						token+= c;
						state= 10;
						break;
					}
					else{//非法符号，报错
						c=fgetc(fpin);
						if(c=='\"'){//若后面还有一个双引号
							state=0;
						}else{//回退
							fseek(fpin, -1, SEEK_CUR);
							state=0;
						}
						raise_error(linenum, 'a');//$ 字符串常量非法字符，a类错误
					}
				}
				break; 
			default://状态不存在，输出错误
				fout << "NO CASE ERROR"<< endl;
				return;
				break;
		}
	}while(ftell(fpin)!=filesize);
}


int lex_top(string inFileName, string outFileName, int md){
	//mode 1:close error output; mode 2: close lex output; mode 3: open all output
	mode= 2;
	cout << "*_Analysis Mode=_"<<mode<<"_*"<<endl;
	//读取文件
	const char* inFileName_c= inFileName.c_str();
	fpin=fopen(inFileName_c, "r"); //打开输入文件
	fout.open(outFileName);//打开输出文件
	cout << "*_Analysis running at_"<<inFileName<<"_*"<<endl;
	cout << "*_Lex analysis start_*"<<endl;
	fseek(fpin,0,SEEK_END);	//移动指针到文件末尾，计算文件大小
    filesize = ftell(fpin);
	cout << "*_Filesize="<< filesize<<"_*" << endl;
    rewind(fpin);//将指针移动到开头
	init();//初始化关键字表、简单符号表
    //运行词法分析
	lex_analyse(); //进行词法分析
	cout << "*_Lex analysis done_*"<<endl;
    //关闭文件
	fclose(fpin);
	fout.close();
	return 0;
}
